﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;
using BasicFramework.Helper;
using BasicFramework.Scenes;
using BasicFramework.Services;
using SlimDX.Windows;
using BasicFramework.Devices;
using BasicFramework.Factory;
using System.IO;
using SlimDX;

namespace BasicFramework
{
    /// <summary>
    /// The Base Class of the Framwork
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class BasicFramework<T> : IDisposable where T : DeviceContextBase, new()
    {

        #region Fields

        private ServiceManager serviceManager;
        private static SceneManager sceneManager;
        private static SceneFactory sceneFactory;
        private FrameworkConfiguration configuration;
        private Form renderForm;
        private static T deviceContext;
        private FormWindowState currentWindowState;
        private bool formIsResizing;
        private bool isFormClosed;
        private TimerClock timer;
        private float frameDelta;
        private bool deviceLost;
        private float frameAccumulator;
        private int frameCount;
        private float framesPerSecond;


        #endregion


        #region Properties

        public FrameworkConfiguration Configuration
        {
            get { return configuration; }
            set { configuration = value; }
        }

        public static T DeviceContext
        {
            get { return deviceContext; }
        }

        public float FramesPerSecond
        {
            get { return framesPerSecond; }
        }

        public static SceneManager SceneManager
        {
            get { return sceneManager; }
        }

        public static SceneFactory SceneFactory
        {
            get { return sceneFactory; }
        }

        public ServiceManager ServiceManager
        {
            get { return serviceManager; }
        }

        #endregion


        #region Constructors

        #endregion


        #region Events

        private void RenderFormResize(object sender, EventArgs e)
        {
            // Check if Window State has changed
            if (this.renderForm.WindowState != this.currentWindowState)
            {
                this.HandleResize();
            }
            // Set new Window State
            this.currentWindowState = this.renderForm.WindowState;
        }

        private void RenderFormResizeEnd(object sender, EventArgs e)
        {
            this.formIsResizing = false;
            this.HandleResize();
        }

        private void RenderFormResizeBegin(object sender, EventArgs e)
        {
            this.formIsResizing = true;
        }

        private void RenderFormFormClosed(object sender, FormClosedEventArgs e)
        {
            this.isFormClosed = true;
        }


        #endregion


        #region Methods

        public void Run()
        {
            // Initialize Logger
            this.InitializeLogger();
            ObjectTracer.InitializeSlimDXDebug();

            this.timer = new TimerClock();
            // Configure Basic Framework
            // Get Framework Configuration
            this.configuration = this.OnConfigure();
            if (configuration == null)
            {
                Logger.Error("No Configuration Objekt specified!");
                Debug.Fail("No Configuration Objekt specified!");
            }
            // Create Render Form
            this.CreateRenderForm();
            // Create an Device Context
            deviceContext = new T();
            // Get current Window State
            this.currentWindowState = this.renderForm.WindowState;
            this.formIsResizing = false;
            // Initialze Event handles
            this.InitializeEvents();
            // Create EntityManager
            this.serviceManager = new ServiceManager(DeviceContext);
            sceneManager = new SceneManager();
            sceneFactory = new SceneFactory(sceneManager);

            this.OnInitialize();
            this.OnResourceLoad();

            this.timer.Start();
            MessagePump.Run(this.renderForm, () =>
                                                 {
                                                     // Cancel if form is closed
                                                     if (this.isFormClosed)
                                                     {
                                                         return;
                                                     }
                                                     this.Update();
                                                     if (!formIsResizing)
                                                     {
                                                         this.Render();
                                                     }

                                                 });

            this.OnResourceUnload();

            Logger.Info("Logging successful");
        }



        private void InitializeLogger()
        {
            StackFrame frame = new StackFrame(2);
            new Logger(frame.GetMethod().Module);
        }

        private void Update()
        {
            this.frameDelta = this.timer.Update();

            SceneManager.Update(this.frameDelta);
            this.OnUpdate(this.frameDelta);
        }


        private void Render()
        {
            if (this.deviceLost)
            {
                DeviceContext.ResetDevice();
                deviceLost = false;
                this.OnResourceLoad();
            }


            //Calculate Frames per Second

            this.frameAccumulator += this.frameDelta;
            ++this.frameCount;
            if (this.frameAccumulator >= 1.0f)
            {
                this.framesPerSecond = this.frameCount / this.frameAccumulator;

                this.frameAccumulator = 0.0f;
                this.frameCount = 0;
            }
            //
            // Start Rendering
            // 
            try
            {
                if (!this.formIsResizing)
                {
                    this.OnRenderBegin();
                    this.OnRender();
                    this.OnRenderEnd();
                }
            }
            catch (SlimDX.Direct3D9.Direct3D9Exception e)
            {
                // Catch exception if device Lost
                if (e.ResultCode == SlimDX.Direct3D9.ResultCode.DeviceLost)
                {
                    this.OnResourceUnload();
                    deviceLost = true;
                }
                else
                {
                    Logger.Exception(e);
                    Logger.Error("Object Leaks:  " + ObjectTracer.ToString());
                }
            }
        }



        private void InitializeEvents()
        {
            Logger.Info("Initialize Events");
            //
            // Mouse Events
            //);
            this.renderForm.MouseClick += new MouseEventHandler(OnMouseClick);
            this.renderForm.MouseMove += new MouseEventHandler(OnMouseMove);
            this.renderForm.MouseDown += new MouseEventHandler(OnMouseDown);
            this.renderForm.MouseUp += new MouseEventHandler(OnMouseUp);
            //
            // Keyboard Events
            //
            this.renderForm.KeyDown += new KeyEventHandler(OnKeyDown);
            this.renderForm.KeyUp += new KeyEventHandler(OnKeyUp);
            this.renderForm.KeyPress += new KeyPressEventHandler(OnKeyPress);

            //
            // Form Events
            //
            this.renderForm.Resize += new EventHandler(RenderFormResize);
            this.renderForm.ResizeBegin += new EventHandler(RenderFormResizeBegin);
            this.renderForm.ResizeEnd += new EventHandler(RenderFormResizeEnd);
            this.renderForm.FormClosed += new FormClosedEventHandler(RenderFormFormClosed);

            Logger.Info("Event initialization successful");

        }


        private void UnsubscribeEvents()
        {
            Logger.Info("Unsubscribe from Events");
            //
            // Mouse Events
            //
            this.renderForm.MouseClick -= OnMouseClick;
            this.renderForm.MouseMove -= OnMouseMove;
            this.renderForm.MouseDown -= OnMouseDown;
            this.renderForm.MouseUp -= OnMouseUp;
            //
            // Keyboard Events
            //
            this.renderForm.KeyDown -= OnKeyDown;
            this.renderForm.KeyUp -= OnKeyUp;
            this.renderForm.KeyPress -= OnKeyPress;
            //
            // Form Events
            //
            this.renderForm.Resize -= RenderFormResize;
            this.renderForm.ResizeBegin -= RenderFormResizeBegin;
            this.renderForm.ResizeEnd -= RenderFormResizeEnd;
            this.renderForm.FormClosed -= RenderFormFormClosed;
            Logger.Info("Unsubscription successful");
        }


        public void InitializeDevice(IDeviceSettings settings)
        {
            if (settings == null)
            {
                Logger.Error("No Device Settings specified!");
                Debug.Fail("No Device Settings specified!");
                throw new ArgumentNullException("settings", "No settings Object specified !");
            }
            // Check matching DX Version
            if (DeviceContext.DXVersion != settings.DXVersion)
            {
                Logger.Error("Framework is not set to an DirectX9 Framework!");
                Debug.Fail("Framework is not set to an DirectX9 Framework!");
                throw new TypeInitializationException("DX9DeviceContext",
                                                      new Exception("Framework is not set to an DirectX9 Framework!"));
            }
            // Initialize Device
            DeviceContext.InitializeDevice(this.renderForm.Handle, settings);
        }

        private void HandleResize()
        {
            // Return if Form is minimized
            if (this.renderForm.WindowState == FormWindowState.Minimized)
            {
                return;
            }

            this.OnResourceUnload();

            // Update Sizes
            this.configuration.Width = this.renderForm.ClientSize.Width;
            this.configuration.Height = this.renderForm.ClientSize.Height;

            if (deviceContext != null)
            {
                DeviceContext.OnResize(this.renderForm.ClientSize);
            }

            OnResourceLoad();

        }




        private void CreateRenderForm()
        {
            Logger.Info("Creating Render Form");
            // Create the new Render form);
            this.renderForm = new RenderForm(this.configuration.Title)
            {
                Width = this.configuration.Width,
                Height = this.configuration.Height
            };
            Logger.Info("Render Form created");

        }


        #endregion

        #region Override Methods

        /// <summary>
        /// Is called first and provides a FrameworkConfiguration Objekt to Initialize the Framework.
        /// If this Method is not handled a basic Configuration is created.
        /// </summary>
        public virtual FrameworkConfiguration OnConfigure()
        {
            return new FrameworkConfiguration();
        }

        public virtual void OnResourceLoad()
        {
            sceneManager.ResourceLoad(true);
        }

        public virtual void OnResourceUnload()
        {
            sceneManager.ResourceUnload(true);
        }

        public virtual void OnRenderEnd()
        {
        }

        public virtual void OnRender()
        {
            sceneManager.Draw(frameDelta);
        }

        public virtual void OnRenderBegin()
        {
        }

        public virtual void OnUpdate(float frame)
        {
        }

        public virtual void OnInitialize()
        {
            // Add default Graphics Service
            this.serviceManager.AddService(new GraphicsService<T>(DeviceContext));
        }

        #endregion

        #region Window Handling events

        public virtual void OnMouseClick(object sender, MouseEventArgs e)
        {
        }

        public virtual void OnMouseMove(object sender, MouseEventArgs e)
        {
        }

        public virtual void OnMouseDown(object sender, MouseEventArgs e)
        {
        }

        public virtual void OnMouseUp(object sender, MouseEventArgs e)
        {
        }

        public virtual void OnKeyPress(object sender, KeyPressEventArgs e)
        {
        }

        public virtual void OnKeyUp(object sender, KeyEventArgs e)
        {
        }

        public virtual void OnKeyDown(object sender, KeyEventArgs e)
        {
        }

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            this.UnsubscribeEvents();
            if (DeviceContext != null)
            {
                DeviceContext.Dispose();
            }
        }

        #endregion
    }
}
